#include <cstdio>
#include <cstdarg>
#include <cstdlib>
#include <cctype>
#include <malloc.h>
#include <string>

#include "mysql.h"
#include "sock.h"
#include "sha1.h"

const int maxinbuflen = 1024;

static void dump(const char* buf, int len, bool toserver);

mysql::datetime::datetime()
{
  tm_isdst = -1; // let system determine if we are in day light saving time zone
  tm_year = tm_mon = tm_mday = tm_wday = tm_yday = tm_hour = tm_min = tm_sec = 0;
}

void
mysql::datetime::set(const char* dt)
{
  tm_isdst = -1; // let system determine if we are in day light saving time zone
  tm_year = tm_mon = tm_mday = tm_wday = tm_yday = tm_hour = tm_min = tm_sec = 0;

  // dt is in YYYY-mm-dd HH:MM:SS format
  char* p = 0;

  while(*dt && !isdigit(*dt)) dt++;
  tm_year = strtoul(dt, &p, 10);
  if (p == dt) return;

  dt = p;
  while(*dt && !isdigit(*dt)) dt++;
  tm_mon = strtoul(dt, &p, 10);
  if (tm_mon > 0) tm_mon -= 1; // month is 0 based
  if (p == dt) return;
  
  dt = p;
  while(*dt && !isdigit(*dt)) dt++;
  tm_mday = strtoul(dt, &p, 10);
  if (p == dt) return;

  dt = p;
  while(*dt && !isdigit(*dt)) dt++;
  tm_hour = strtoul(dt, &p, 10);
  if (p == dt) return;

  dt = p;
  while(*dt && !isdigit(*dt)) dt++;
  tm_min = strtoul(dt, &p, 10);
  if (p == dt) return;

  dt = p;
  while(*dt && !isdigit(*dt)) dt++;
  tm_sec = strtoul(dt, &p, 10);
  if (p == dt) return;

  // sanity check values
  if (tm_year > 1900) tm_year -= 1900;
}

void
mysql::datetime::set(time_t dt)
{
  *(struct tm*)this = *localtime(&dt);
}

time_t
mysql::datetime::get_time_t() const
{
  struct tm dt = *this; // mktime takes in non-const struct tm
  time_t dtime = mktime(&dt);
  if (dtime == -1) dtime = 0;
  return dtime;
}

std::string
mysql::datetime::get_time_string() const
{
  char ts[26];
  strftime(ts, 26, "%Y-%m-%d %H:%M:%S", this);
  return std::string(ts);
}

void
mysql::decimal::set(const char* d)
{
  value.clear();
  value.insert(value.end(), length+1, 0);
  
  while(*d && *d != '-' && !isdigit(*d)) d++;
  if (*d == '-') {
    d++;
    value[0] = 1;
  } else
    value[0] = 0;

  const char* p = strchr(d, '.');
  const char* q = p;
  if (p) {
    p++;
    const char* e = p + precision;
    int vi = length - precision + 1;
    while(*p && p < e)
      value[vi++] = *p++ - '0';
  }

  int vi = length - precision;
  while(vi > 0 && q-- > d)
    value[vi--] = *q - '0';
}

void
mysql::decimal::set(double d)
{
  value.clear();
  value.insert(value.end(), length+1, 0);
  value[0] = d < 0 ? 1: 0;

  int vi = length - precision;
  if (d < 0) d = -d;
  __int64 dint = (__int64)d;
  __int64 saved_dint = dint;
  while(dint && vi > 0) {
    value[vi] = (unsigned char)(dint % 10);
    dint /= 10;
    vi--;
  }

  if (dint) throw exception("mysql::decimal::set(double): overflow occurred while converting %lf to decimal", value[0] * d);

  d -= (double)saved_dint;
  vi = length - precision;
  while(vi++ < length) {
    d *= 10;
    int dval = (unsigned int)d % 10;
    value[vi] = dval;
  }
}

mysql::decimal::operator double() const
{
  double d = 0;
  double ptfix = 1;
  int    ptlen = length - precision;
  int    vi = 1;

  for(; vi <= ptlen; vi++)
    d = d * 10 + value[vi];

  for(; vi <= length; vi++) {
    d = d * 10 + value[vi];
    ptfix *= 10;
  }

  d /= ptfix;
  if (value[0] == 1) d = -d;
  return d;
}

mysql::decimal::operator std::string() const
{
  char* buf = (char*)alloca(length+3);
  char* d = buf;

  if (value[0] == 1) *d++ = '-';

  int   ptlen = length - precision;
  int   vi = 1;
  while(vi <= ptlen) {
    if (value[vi])
      *d++ = '0' + value[vi];
    vi++;
  }

  char* pt = d++;
  *pt = 0;

  while(vi <= length) {
    *d++ = '0' + value[vi];
    if (value[vi]) *pt = '.';
    vi++;
  }

  *d = 0;

  return std::string(buf);
}

mysql::mysql()
: sock(0),
  outpktnum(0),
  inbufend(0),
  inbufbeg(0),
  inlastpkt(0),
  inpkttype(mysql::pt_none),
  inpktsize(0),
  inpktnum(0),
  inbytesleft(0),
  serverport(3306),
  threadid(0),
  capabilities(mysql::cap_long_password | mysql::cap_long_flag | mysql::cap_transactions | mysql::cap_local_files),
  charset(mysql::cs_latin1),
  version(mysql::version10),
  verbose(0)
{
  serverhost[0] = username[0] = password[0] = 0;
}

mysql::mysql(const char*      serverhostname,
             unsigned short   port,
             const char*      user,
             const char*      pwd,
             mysql::myversion ver,
             int              dumpflag)
: sock(0),
  outpktnum(0),
  inbufend(0),
  inbufbeg(0),
  inlastpkt(0),
  inpkttype(mysql::pt_none),
  inpktsize(0),
  inpktnum(0),
  inbytesleft(0),
  serverport(port),
  threadid(0),
  capabilities(mysql::cap_long_password | mysql::cap_long_flag | mysql::cap_transactions | mysql::cap_local_files),
  charset(mysql::cs_latin1),
  version(ver),
  verbose(dumpflag)
{
  connect(serverhostname, port, user, pwd, ver);
}

mysql::~mysql()
{
  disconnect();
}

void
mysql::connect(const char* serverhostname,
               unsigned short server_port,
               const char* user,
               const char* pwd,
               mysql::myversion ver)
{
  serverhost[0] = username[0] = password[0] = 0;

  serverport = server_port;
  version    = ver;

  if (serverhostname == 0) serverhostname = "";
  if (serverport == 0) serverport = 3306;
  if (user == 0) user = "";
  if (pwd == 0) pwd = "";

  strcpy(serverhost, serverhostname);
  strcpy(username, user);
  strcpy(password, pwd);

  reconnect();
}

void
mysql::disconnect()
{
  if (sock == 0) return;

  if (outbuf.size() == 4) {
    try {
      // send a quit message
      put_command(cmd_quit);
      send_packet();
    }
    catch(...) {} // ignore all errors during shutdown
  }
  
  shutdown(sock, SD_BOTH);
  closesocket(sock);
  sock = 0;
}

void
mysql::reconnect()
{
  disconnect();

  outbuf.clear();
  outbuf.reserve(1024);
  outbuf.insert(outbuf.end(), 4, 0);

  response.clear();
  
  outpktnum   = 0;
  inbufbeg    = 0;
  inbufend    = 0;
  inlastpkt   = 0;
  inpkttype   = pt_none;
  inpktnum    = 0;
  inpktsize   = 0;
  inbytesleft = 0;

  // check assumptions
  if (sizeof(unsigned char) != 1)
    throw exception("invalid assumption: sizeof unsigned char is not 1");
  if (sizeof(short) != 2)
    throw exception("invalid assumption: sizeof short is not 2");
  if (sizeof(int) != 4)
    throw exception("invalid assumption: sizeof int is not 4");
  if (sizeof(long) != 4)
    throw exception("invalid assumption: sizeof int is not 4");

  // get server address
  unsigned long peeraddr = inet_addr(serverhost);
  if (peeraddr == -1) {
    struct hostent* hent = gethostbyname(serverhost);
    if (hent == 0)
      throw socklib::exception("unable to resolve input hostname: %s; $m", serverhost);
    memcpy(&peeraddr, hent->h_addr, hent->h_length);
  }

  struct sockaddr_in peer;
  peer.sin_addr.s_addr = peeraddr;
  peer.sin_family      = AF_INET;
  peer.sin_port        = htons(serverport);

  sock = ::socket(AF_INET, SOCK_STREAM, 0);
  if (sock == INVALID_SOCKET)
    throw socklib::exception("mysql:reconnect: socket: $s");

  struct linger sockopt;
  sockopt.l_onoff  = 1;
  sockopt.l_linger = 0;

  if (::setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char*)&sockopt, sizeof(struct linger)) == SOCKET_ERROR)
    throw socklib::exception("mysql:reconnect: setsockopt(SO_LINGER): $s");

  int retry_count = 10;
  while(::connect(sock, (struct sockaddr*)&peer, sizeof(peer)) == SOCKET_ERROR) {
    int err = WSAGetLastError();
    if (retry_count-- && err == WSAEADDRINUSE) {
      Sleep(100);
      continue;
    }
    throw socklib::exception("mysql:reconnect: connect(%s): $s", serverhost);
  }

  // process helo message from server
  unsigned char protocol = get_byte();
  if (protocol == 0xff) {
    // an error occurred
    unget_byte();
    process_response();
    return;
  }

  serverversion = get_string();
  threadid      = get_int();
  salt          = get_string();
  capabilities  = get_ushort();
  charset       = (char_set)get_byte();

  response.pkttype = pt_connect;
  response.status  = get_short();

  if ((capabilities & cap_secure_auth) && inbufend-inbufbeg == 26) {
    trash(13);
    salt = salt + get_string(); // 12 bytes of additional salt
  } else
    trash();

  // send login packet
  put_ushort(cap_long_password | cap_long_flag | cap_transactions | cap_local_files); // client capabilities

  put_int24(0); // max packet size
  put_str0(username);

  // scramble password
  std::string scrambled_password = capabilities & cap_secure_auth
    ? password_digest5(password, salt.c_str())
    : password_digest4(password, salt.c_str());
  put_string(scrambled_password);

  send_packet(inpktnum+1);

  process_response();
}

void
mysql::process_response()
{
  switch(response.pkttype) {
  case pt_fields:
    process_fields();
    return;

  case pt_rows:
    process_rows();
    return;
  }

  int packet_type = get_byte();

  switch(packet_type) {
  case pt_okay:
    response.pkttype       = pt_okay;
    response.affected_rows = get_field_length();
    response.insert_id     = get_field_length();

    if (inbytesleft >= 2)
      response.status        = get_short();

    switch(response.cmdtype) {
    case cmd_query:
    case cmd_field_list:
    case cmd_process_info:
      // tabular response
      response.rescode = rc_no_more_results;
      break;

    default:
      response.rescode = rc_success;
    }

    return;

  case pt_send_file:
    response.pkttype       = pt_send_file;
    response.rescode       = rc_send_file;
    return;

  case pt_error:
    {
      response.pkttype = pt_error;
      response.rescode = rc_fail;

      unsigned short errcode = get_ushort();
      int   msglen = inavail();
      char* msg = (char*)alloca(msglen + 1);
      get_string(msg, msglen);
      msg[msglen] = 0;
      throw exception("server error (%d): %s", errcode, msg);
    }
    return;

  default:
    unget_byte();
    switch(response.cmdtype) {
    case cmd_statistics:
      response.stats   = get_length_and_string();
      response.rescode = rc_success;
      return;

    case cmd_query:
    case cmd_field_list:
    case cmd_process_info:
      // tabular response
      response.pkttype = pt_fields;
      response.rescode = rc_success;
      response.field_count = get_field_length();
      fields.clear();
      fields.current_column = 0;
      return;
    }

  }
}

void
mysql::process_fields()
{
  unsigned char packet_type = get_byte();
  if (packet_type == pt_end) {
    // finished reading all fields
    if (response.cmdtype == cmd_field_list) {
      response.rescode = rc_no_more_results;
      return;
    }

    // start reading rows
    response.pkttype = pt_rows;
    fields.current_column = 0; // so that bind can happen
    return;
  }

  unget_byte(); // put the byte back
  
  field_info& field = *fields.insert(fields.end(), field_info());
  field.table   = get_length_and_string();
  field.name    = get_length_and_string();
  field.length  = get_length_and_int();
  field.fldtype = (field_type)get_length_and_int();

  unsigned int flag_decimal = get_length_and_int();
  field.flags    = flag_decimal & 0xffff;
  field.decimals = flag_decimal >> 16;

  if (inavail()) field.default_value = get_length_and_string();
  trash(); // trash remaining bytes

  fields.current_column++;
}

void
mysql::process_rows()
{
  if (fields.current_column != fields.size()) 
    throw exception("process_rows: #fields=%d  !=  #bound_fields=%d",
                    fields.size(), fields.current_column);

  unsigned char packet_type = get_byte();
  if (packet_type == pt_end) {
    // finished reading all rows
    response.pkttype = pt_end;
    response.rescode = rc_no_more_rows;
    return;
  }

  unget_byte();

  for(field_infos::iterator fi = fields.begin(); fi != fields.end(); fi++) {
    field_info& field = *fi;
    read_field_value(field);
  }
}

mysql::result_code
mysql::getresult()
{
  int field_count = 0;

  while (response.pkttype == pt_fields) {
    // read the fields
    process_response();    
    field_count++;
  }

  if (field_count && fields.size() == response.field_count) return rc_success;

  return rc_no_more_results;
}

mysql::result_code
mysql::getrow()
{
  if (response.pkttype == pt_rows) {
    process_response();
    return response.rescode;
  }

  return rc_no_more_rows;
}

void
mysql::read_field_value(field_info& field)
{
  // check if field is null
  unsigned char b = get_byte();
  if (b == 251) {
    field.null_value = true;
    return;
  }
  
  unget_byte(); // put back the byte 
  field.null_value = false;

  switch(field.datatype) {
  case dt_char: read_field_char_value(field); return;
  case dt_short: read_field_short_value(field); return;
  case dt_int: read_field_int_value(field); return;
  case dt_int64: read_field_int64_value(field); return;
  case dt_float: read_field_float_value(field); return;
  case dt_double: read_field_double_value(field); return;
  case dt_time: read_field_time_value(field); return;
  case dt_decimal: read_field_decimal_value(field); return;
  case dt_string: read_field_string_value(field); return;
  case dt_chars: read_field_chars_value(field); return;
  case dt_voids: read_field_voids_value(field); return;
  case dt_bytes: read_field_bytes_value(field); return;
  case dt_qstring: read_field_qstring_value(field); return;
  default: break;
  }

  throw exception("read_field_value: type %s of bound variable for field %s.%s not implemented",
                    data_type2str(field.datatype), field.table.c_str(), field.name.c_str());
}

void
mysql::read_field_char_value(mysql::field_info& field)
{
  int fldlen = (int)get_field_length();
  char* buf = (char*)alloca(fldlen+1);
  get(buf, fldlen);
  buf[fldlen] = 0;

  *(char*)field.data = atoi(buf);
}

void
mysql::read_field_short_value(mysql::field_info& field)
{
  int fldlen = (int)get_field_length();
  char* buf = (char*)alloca(fldlen+1);
  get(buf, fldlen);
  buf[fldlen] = 0;

  *(short*)field.data = atoi(buf);
}

void
mysql::read_field_int_value(mysql::field_info& field)
{
  int fldlen = (int)get_field_length();
  char* buf = (char*)alloca(fldlen+1);
  get(buf, fldlen);
  buf[fldlen] = 0;

  switch(field.fldtype) {
  case ft_time:
    // HHH:MM:SS format
    {
      int hhh = 0;
      int mm  = 0;
      int ss  = 0;
      sscanf(buf, "%d:%d:%d", &hhh, &mm, &ss);
      *(int*)field.data = hhh < 0 ? -(-hhh * 3600 + mm * 60) + ss: hhh * 3600 + mm * 60;
    }
    return;
  }

  *(int*)field.data = atoi(buf);
}

void
mysql::read_field_int64_value(mysql::field_info& field)
{
  int fldlen = (int)get_field_length();
  char* buf = (char*)alloca(fldlen+1);
  get(buf, fldlen);
  buf[fldlen] = 0;

  *(__int64*)field.data = _atoi64(buf);
}

void
mysql::read_field_float_value(mysql::field_info& field)
{
  int fldlen = (int)get_field_length();
  char* buf = (char*)alloca(fldlen+1);
  get(buf, fldlen);
  buf[fldlen] = 0;

  *(float*)field.data = (float)atof(buf);
}

void
mysql::read_field_double_value(mysql::field_info& field)
{
  int fldlen = (int)get_field_length();
  char* buf = (char*)alloca(fldlen+1);
  get(buf, fldlen);
  buf[fldlen] = 0;

  *(double*)field.data = atof(buf);
}

void
mysql::read_field_time_value(mysql::field_info& field)
{
  int fldlen = (int)get_field_length();
  char* buf = (char*)alloca(fldlen+1);
  get(buf, fldlen);
  buf[fldlen] = 0;

  datetime& dt = *(datetime*)field.data;
  dt.set(buf);
}

void
mysql::read_field_decimal_value(mysql::field_info& field)
{
  int fldlen = (int)get_field_length();
  char* buf = (char*)alloca(fldlen+1);
  get(buf, fldlen);
  buf[fldlen] = 0;

  decimal& dt = *(decimal*)field.data;
  dt.set(buf);
}

void
mysql::read_field_string_value(mysql::field_info& field)
{
  *(std::string*)field.data = get_length_and_string();
}

void
mysql::read_field_chars_value(mysql::field_info& field)
{
  int fldlen = (int)get_field_length();
  if (fldlen > field.datalen) {
    get_string((char*)field.data, field.datalen);
    trash(fldlen - field.datalen);
  } else {
    get_string((char*)field.data, fldlen);
    memset((char*)field.data + fldlen, 0, field.datalen - fldlen);
  }
}

void
mysql::read_field_voids_value(mysql::field_info& field)
{
  int fldlen = (int)get_field_length();
  if (fldlen > field.datalen) {
    get(field.data, field.datalen);
    trash(fldlen - field.datalen);
  } else {
    get_string((char*)field.data, fldlen);
    memset((char*)field.data + fldlen, 0, field.datalen - fldlen);
  }
}

void
mysql::read_field_bytes_value(mysql::field_info& field)
{
  socklib::bytes& d = *(socklib::bytes*)field.data;
  get_length_and_bytes(d);
}

void
mysql::read_field_qstring_value(mysql::field_info& field)
{
  std::string& str = *(std::string*)field.data;
  str = get_length_and_string();

  // determine if the string needs to be quoted
  switch(field.fldtype) {
  case ft_null:
    str = "NULL";
    break;

  case ft_date:
  case ft_time:
  case ft_datetime:
  case ft_year:
  case ft_newdate:
  case ft_enum:
  case ft_set:
    str = "'" + str + "'";
    break;

  case ft_tiny_blob:
  case ft_medium_blob:
  case ft_long_blob:
  case ft_blob:
  case ft_var_string:
  case ft_string:
  case ft_geometry:
    str = "'" + quote_string(str.c_str()) + "'";
    break;

  default:
    break;
  }
}

void
mysql::select_db(const char* dbname)
{
  put_command(cmd_init_db);
  put_str(dbname);
  send_packet();
  process_response();
}

mysql::result_code
mysql::execf(const char* fmt, ...)
{
  char buf[16386];
  
  va_list ap;
  va_start(ap, fmt);
  _vsnprintf(buf, 16386, fmt, ap);
  va_end(ap);

  return exec(buf);
}

mysql::result_code
mysql::exec(const char* cmd)
{
  // trash old query results if there were any
  //while(response.more_results)
  //  getresult();

  if (verbose & vdump_queries)
    printf("QUERY: %s\n", cmd);

  put_command(cmd_query);
  put_str(cmd);
  send_packet();
  process_response();
  return response.rescode;
}

void
mysql::bind(char& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];

  if (field.flags & ff_unsigned)
    throw exception("bind: unable to bind signed variable to unsigned field %s.%s",
                    field.table.c_str(), field.name.c_str());
  
  switch(field.fldtype) {
  case ft_tiny:
    field.datatype = dt_char;
    field.datalen  = sizeof(char);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to char for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(unsigned char& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];

  if (!(field.flags & ff_unsigned))
    throw exception("bind: unable to bind unsigned variable to signed field %s.%s",
                    field.table.c_str(), field.name.c_str());
  
  switch(field.fldtype) {
  case ft_tiny:
    field.datatype = dt_char;
    field.datalen  = sizeof(char);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to unsigned char for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(short& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];

  if (field.flags & ff_unsigned)
    throw exception("bind: unable to bind signed variable to unsigned field %s.%s",
                    field.table.c_str(), field.name.c_str());
  
  switch(field.fldtype) {
  case ft_tiny:
  case ft_short:
    field.datatype = dt_short;
    field.datalen  = sizeof(short);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to short for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(unsigned short& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];

  if (!(field.flags & ff_unsigned))
    throw exception("bind: unable to bind unsigned variable to signed field %s.%s",
                    field.table.c_str(), field.name.c_str());
  
  switch(field.fldtype) {
  case ft_tiny:
  case ft_short:
    field.datatype = dt_short;
    field.datalen  = sizeof(short);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to unsigned short for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(int& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];

  if (field.flags & ff_unsigned)
    throw exception("bind: unable to bind signed variable to unsigned field %s.%s",
                    field.table.c_str(), field.name.c_str());
  
  switch(field.fldtype) {
  case ft_tiny:
  case ft_short:
  case ft_int24:
  case ft_long:
  case ft_year:
  case ft_time:
    field.datatype = dt_int;
    field.datalen  = sizeof(int);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to int for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(unsigned int& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];

  if (!(field.flags & ff_unsigned))
    throw exception("bind: unable to bind unsigned variable to signed field %s.%s",
                    field.table.c_str(), field.name.c_str());
  
  switch(field.fldtype) {
  case ft_tiny:
  case ft_short:
  case ft_int24:
  case ft_long:
  case ft_year:
  case ft_time:
    field.datatype = dt_int;
    field.datalen  = sizeof(int);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to unsigned int for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(__int64& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];

  if (field.flags & ff_unsigned)
    throw exception("bind: unable to bind signed variable to unsigned field %s.%s",
                    field.table.c_str(), field.name.c_str());
  
  switch(field.fldtype) {
  case ft_tiny:
  case ft_short:
  case ft_int24:
  case ft_long:
  case ft_longlong:
    field.datatype = dt_int64;
    field.datalen  = sizeof(__int64);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to __int64 for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(unsigned __int64& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];

  if (!(field.flags & ff_unsigned))
    throw exception("bind: unable to bind unsigned variable to signed field %s.%s",
                    field.table.c_str(), field.name.c_str());
  
  switch(field.fldtype) {
  case ft_tiny:
  case ft_short:
  case ft_int24:
  case ft_long:
  case ft_longlong:
    field.datatype = dt_int64;
    field.datalen  = sizeof(__int64);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to unsigned __int64 for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(float& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];
  
  switch(field.fldtype) {
  case ft_float:
  case ft_double:
  case ft_decimal:
    field.datatype = dt_float;
    field.datalen  = sizeof(float);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to float for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(double& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];
  
  switch(field.fldtype) {
  case ft_float:
  case ft_double:
  case ft_decimal:
    field.datatype = dt_double;
    field.datalen  = sizeof(double);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to double for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(mysql::datetime& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];
  
  switch(field.fldtype) {
  case ft_date:
  case ft_datetime:
  case ft_newdate:
  case ft_timestamp:
    field.datatype = dt_time;
    field.datalen  = sizeof(time_t);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to datetime for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(mysql::decimal& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];

  switch(field.fldtype) {
  case ft_decimal:
    if (d.length+2 != field.length || d.precision != field.decimals)
      throw exception("bind: type mismatch: %s.%s is DECIMAL(%d,%d); bind variable is decimal(%d,%d)",
                      field.table.c_str(), field.name.c_str(), field.length-2, field.decimals, d.length, d.precision);
  
    field.datatype = dt_decimal;
    field.datalen  = sizeof(decimal);
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to decimal for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(char* d, int len)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];
  
  switch(field.fldtype) {
  case ft_decimal:
  case ft_timestamp:
  case ft_date:
  case ft_time:
  case ft_datetime:
  case ft_year:
  case ft_newdate:
  case ft_enum:
  case ft_set:
  case ft_tiny_blob:
  case ft_medium_blob:
  case ft_long_blob:
  case ft_blob:
  case ft_var_string:
  case ft_string:
    field.datatype = dt_chars;
    field.datalen  = len-1;
    field.data     = d;
    d[len-1] = 0;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to char[%d] for field %s.%s",
	                  field_type2str(field.fldtype), len, field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(std::string& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];
  
  switch(field.fldtype) {
  case ft_decimal:
  case ft_timestamp:
  case ft_date:
  case ft_time:
  case ft_datetime:
  case ft_year:
  case ft_newdate:
  case ft_enum:
  case ft_set:
  case ft_tiny_blob:
  case ft_medium_blob:
  case ft_long_blob:
  case ft_blob:
  case ft_var_string:
  case ft_string:
    field.datatype = dt_string;
    field.datalen  = 0;
    field.data     = &d;
    fields.current_column++;
    break;

  default:
    throw exception("bind: unable to convert %s to string for field %s.%s",
	                  field_type2str(field.fldtype), field.table.c_str(), field.name.c_str());
  }
}

void
mysql::bind(void* d, int len)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];
  
  field.datatype = dt_voids;
  field.datalen  = len;
  field.data     = &d;
  fields.current_column++;
}

void
mysql::bind(socklib::bytes& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];
  
  field.datatype = dt_bytes;
  field.datalen  = 0;
  field.data     = &d;
  fields.current_column++;
}

void
mysql::bind_qstring(std::string& d)
{
  if (fields.current_column == fields.size()) 
    throw exception("bind: trying to bind more than %d columns", fields.size());

  field_info& field = fields[fields.current_column];
  
  field.datatype = dt_qstring;
  field.datalen  = 0;
  field.data     = &d;
  fields.current_column++;
}

void
mysql::put_command(mysql::command cmd)
{
  if (outbuf.size() != 4) throw exception("put_command: previous packet not yet sent");
  
  trash(); // trash any input that was not used up

  response.clear();
  response.cmdtype = cmd;
  
  put_uchar(cmd);
}

void
mysql::put(unsigned char d)
{
  put((void*)&d, 1);
}

void
mysql::put(short d)
{
  unsigned char b[2];
  b[0] = d & 0xff;
  b[1] = d >> 8;
  put((void*)b, 2);
}

void
mysql::put(unsigned short d)
{
  unsigned char b[2];
  b[0] = d & 0xff;
  b[1] = d >> 8;
  put((void*)b, 2);
}

void
mysql::put_int24(int d)
{
  unsigned char b[3];
  b[0] = d & 0xff;
  b[1] = (d >> 8) & 0xff;
  b[2] = (d >> 16) & 0xff;
  put((void*)b, 3);
}

void
mysql::put(int d)
{
  unsigned char b[4];
  b[0] = d & 0xff;
  b[1] = (d >> 8) & 0xff;
  b[2] = (d >> 16) & 0xff;
  b[3] = (d >> 24) & 0xff;
  put((void*)b, 4);
}

void
mysql::put(unsigned int d)
{
  unsigned char b[4];
  b[0] = d & 0xff;
  b[1] = (d >> 8) & 0xff;
  b[2] = (d >> 16) & 0xff;
  b[3] = (d >> 24) & 0xff;
  put((void*)b, 4);
}

void
mysql::put(__int64 d)
{
  unsigned char b[4];
  b[0] = (unsigned char)(d & 0xff);
  b[1] = (unsigned char)((d >> 8) & 0xff);
  b[2] = (unsigned char)((d >> 16) & 0xff);
  b[3] = (unsigned char)((d >> 24) & 0xff);
  b[4] = (unsigned char)((d >> 32) & 0xff);
  b[5] = (unsigned char)((d >> 40) & 0xff);
  b[6] = (unsigned char)((d >> 48) & 0xff);
  b[7] = ((unsigned char)(d >> 56) & 0xff);
  put((void*)b, 8);
}

void
mysql::put(unsigned __int64 d)
{
  unsigned char b[4];
  b[0] = (unsigned char)(d & 0xff);
  b[1] = (unsigned char)((d >> 8) & 0xff);
  b[2] = (unsigned char)((d >> 16) & 0xff);
  b[3] = (unsigned char)((d >> 24) & 0xff);
  b[4] = (unsigned char)((d >> 32) & 0xff);
  b[5] = (unsigned char)((d >> 40) & 0xff);
  b[6] = (unsigned char)((d >> 48) & 0xff);
  b[7] = ((unsigned char)(d >> 56) & 0xff);
  put((void*)b, 8);
}

void
mysql::put(const void* d, int len)
{
  outbuf.insert(outbuf.end(), (unsigned char*)d, (unsigned char*)d + len);
}

void
mysql::put(const void* d, int len, int forcelen)
{
  int zerolen = forcelen - len;
  if (zerolen < 0) {
    zerolen = 0;
    len = forcelen;
  }

  put(d, len);
  
  if (zerolen > 0) {
    char* dz = (char*)alloca(zerolen);
    memset(dz, 0, zerolen);
    put(dz, zerolen);
  }
}

void
mysql::put_field_length(unsigned int len)
{
  if (len <= 250) put((unsigned char)len);
  else if (len <= 0xFFFF) {
    put((unsigned char)252);
    put((unsigned short)len);
  } else if (len <= 0xFFFFFF) {
    put((unsigned char)253);
    unsigned char b[4];
    b[0] = len & 0xff;
    b[1] = len >> 8 & 0xff;
    b[2] = len >> 16 & 0xff;
    put((void*)b, 3);
  } else {
    put((unsigned char)254);
    unsigned char b[8];
    b[0] = len & 0xff;
    b[1] = len >> 8 & 0xff;
    b[2] = len >> 16 & 0xff;
    b[3] = len >> 24 & 0xff;
    b[4] = 0;
    b[5] = 0;
    b[6] = 0;
    b[7] = 0;
    put((void*)b, 8);
  }
}

void
mysql::send_packet(int pktnum)
{
  int size = (int)outbuf.size();
  int bodysize = size - 4;
  outbuf[0] = bodysize       & 0xff;
  outbuf[1] = bodysize >>  8 & 0xff;
  outbuf[2] = bodysize >> 16 & 0xff;

  outbuf[3] = outpktnum = pktnum;

  const char* ci = (const char*)&*outbuf.begin();

  if (verbose & vdump_packets) dump(ci, size, true);

  int wlen = 0;
  while(wlen < size) {
    int wr = send(sock, ci, size-wlen, 0);
    if (wr == -1)
      throw socklib::exception("mysql:send_packet: send: $s");
    wlen += wr;
    ci = ci + wr;
  }

  outbuf.clear();
  outbuf.insert(outbuf.end(), 4, 0);
}

unsigned char
mysql::get_byte()
{
  unsigned char d = 0;
  get((void*)&d, 1);
  return d;
}

short
mysql::get_short()
{
  unsigned char b[2];
  get((void*)b, 2);

  short d = (short)((unsigned short)b[1] << 8 | b[0]);
  return d;
}

unsigned short
mysql::get_ushort()
{
  unsigned char b[2];
  get((void*)b, 2);

  unsigned short d = (unsigned short)((unsigned short)b[1] << 8 | b[0]);
  return d;
}

int
mysql::get_int24()
{
  unsigned char b[3];
  get((void*)b, 3);

  int d = (int)((unsigned int)b[2] << 16 | (unsigned int)b[1] << 8 | b[0]);
  return d;
}

int
mysql::get_int()
{
  unsigned char b[4];
  get((void*)b, 4);

  int d = (int)((unsigned int)b[3] << 24 | (unsigned int)b[2] << 16 | (unsigned int)b[1] << 8 | b[0]);
  return d;
}

unsigned int
mysql::get_uint()
{
  unsigned char b[4];
  get((void*)b, 4);

  unsigned int d = (unsigned int)((unsigned int)b[3] << 24 | (unsigned int)b[2] << 16 | (unsigned int)b[1] << 8 | b[0]);
  return d;
}

int
mysql::get_length_and_int()
{
  int fldlen = (int)get_field_length();

  switch(fldlen) {
  case 0: return 0;
  case 1: return get_byte();
  case 2: return get_short();
  case 3: return get_int24();
  case 4: return get_int();
  default:
    trash(fldlen);
  }

  return 0;
}

std::string
mysql::get_length_and_string()
{
  int len = (int)get_field_length();
  std::string d("");
  d.reserve(len+1);

  while(len--) {
    unsigned char b = get_byte();
    d.append(1, b);
  }

  return d;
}

std::string
mysql::get_string()
{
  std::string d("");

  for(;;) {
    unsigned char b = get_byte();
    if (b == 0) break;
    d.append(1, b);
  }

  return d;
}

void
mysql::get_string(char* d, int forcelen)
{
  memset(d, 0, forcelen);
  get(d, forcelen);
}

void
mysql::get_length_and_bytes(socklib::bytes& d)
{
  int len = (int)get_field_length();
  d.reserve(len);

  while(len--) {
    unsigned char b = get_byte();
    d.insert(d.end(), b);
  }
}

void
mysql::get(void* d, int len)
{
  while(len) {
    int availlen = inbufend - inbufbeg;
    if (availlen >= len) {
      memcpy(d, inbuf+inbufbeg, len);
      inbufbeg += len;
      return;
    } else {
      memcpy(d, inbuf+inbufbeg, availlen);
      inbufbeg += availlen;
      len -= availlen;
      d = (char*)d + availlen;
      recv_inbuf();
    }
  }
}

__int64
mysql::get_field_length()
{
  __int64 b[8];
  b[0] = get_byte();
  if (b[0] <= 250) return b[0];

  switch(b[0]) {
  case 252:
    b[0] = get_byte();
    b[1] = get_byte();
    return b[1] << 8 | b[0];

  case 253:
    b[0] = get_byte();
    b[1] = get_byte();
    b[2] = get_byte();
    return b[2] << 16 | b[1] << 8 | b[0];

  case 254:
    b[0] = get_byte();
    b[1] = get_byte();
    b[2] = get_byte();
    b[3] = get_byte();
    b[4] = get_byte();
    b[5] = get_byte();
    b[6] = get_byte();
    b[7] = get_byte();
    return b[7] << 56 | b[6] << 48 | b[5] << 40 | b[4] << 32 | b[3] << 24 | b[2] << 16 | b[1] << 8 | b[0];
  }

  throw exception("get_field_length: ill formed field length data");
}


void
mysql::trash()
{
  int len = inavail();
  trash(len);
}

void
mysql::trash(int len)
{
  while(len) {
    int availlen = inbufend - inbufbeg;
    if (availlen > len) availlen = len;
    
    inbufbeg += availlen;
    len -= availlen;

    if (len > 0)
      recv_inbuf();
  }
}

void
mysql::recv_inbuf()
{
  // if bytes from previous packet are still in socket buffer,
  // read those bytes first.
  // otherwise, read a new packet.

  if (inbufbeg) {
    memmove(inbuf, inbuf+inbufbeg, inbufend-inbufbeg);
    inbufend -= inbufbeg;
    inbufbeg = 0;
  }

  int availbuflen = maxinbuflen - inbufend;

  if (inbytesleft == 0) {
    // fresh packet
    unsigned char b[4];
    int rd = 0;
    while (rd < 4) {
      int rdlen = recv(sock, (char*)b+rd, 4-rd, 0);
      if (rdlen == -1)
        throw socklib::exception("mysql:recv_inbuf: recv1: $s");
      rd += rdlen;
    }

    inpktsize = inbytesleft = (int)((unsigned int)b[2] << 16 | (unsigned int)b[1] << 8 | b[0]);
    inpktnum  = (int)(unsigned int)b[3];

    if (verbose & vdump_packets) dump((char*)b, 4, false);
  }

  if (availbuflen > inbytesleft) availbuflen = inbytesleft;

  while (availbuflen > 0) {
    // still bytes are left from previous packet
    int rd = recv(sock, (char*)inbuf+inbufend, availbuflen, 0);
    if (rd == -1)
      throw socklib::exception("mysql:recv_inbuf: recv2: $s");
    availbuflen -= rd;
    inbufend += rd;
    inbytesleft -= rd;
  }

  if (verbose & vdump_packets) dump((const char*)inbuf, inbufend, false);
}

const char*
mysql::field_type2str(mysql::field_type fldtype)
{
  switch(fldtype) {
  case ft_decimal: return "decimal";
  case ft_tiny: return "tiny";
  case ft_short: return "short";
  case ft_long: return "long";
  case ft_float: return "float";
  case ft_double: return "double";
  case ft_null: return "null";
  case ft_timestamp: return "timestamp";
  case ft_longlong: return "longlong";
  case ft_int24: return "int24";
  case ft_date: return "date";
  case ft_time: return "time";
  case ft_datetime: return "datetime";
  case ft_year: return "year";
  case ft_newdate: return "newdate";
  case ft_enum: return "enum";
  case ft_set: return "set";
  case ft_tiny_blob: return "tiny_blob";
  case ft_medium_blob: return "medium_blob";
  case ft_long_blob: return "long_blob";
  case ft_blob: return "blob";
  case ft_var_string: return "var_string";
  case ft_string: return "string";
  case ft_geometry: return "geometry";
  }

  return "unknown-type";
}

const char*
mysql::data_type2str(mysql::data_type datatype)
{
  switch(datatype) {
  case dt_none: return "none";
  case dt_char: return "char";
  case dt_short: return "short";
  case dt_int: return "int";
  case dt_int64: return "int64";
  case dt_float: return "float";
  case dt_double: return "double";
  case dt_time: return "mysql::datetime";
  case dt_decimal: return "mysql::decimal";
  case dt_string: return "std::string";
  case dt_chars: return "char*";
  case dt_voids: return "void*";
  case dt_bytes: return "socklib::bytes";
  }

  return "unknown-type";
}


/////////////////////////////////////////////////////////////
//////////////////////////////////////////////PASSWORD DIGEST

mysql::rand
mysql::rand::operator^(const mysql::rand& b) const
{
  rand r;

  r.m = m ^ b.m;
  r.n = n ^ b.n;
  return r;
}

double
mysql::rand::gen_rand()
{
  unsigned long mv = 0x3FFFFFFFUL;
  m = (m * 3 + n) % mv;
  n = (m + n + 33) % mv;
  return (double)m/(double)mv;
}

void
mysql::rand::hash(const char* password)
{ 
  m = 1345345333UL;
  n = 0x12345671UL;
  unsigned long add = 7;
  
  while(*password) {
    unsigned long c = (unsigned char)*password++;
    if (c == ' ' || c == '\t') continue; // ignore space and tab
    
    m  ^= ((m & 63) + add) * c + (m << 8);
    n  += (n << 8) ^ m; // += is used in libmysql; = is used by Ian Redfern
    add += c;
  }

  m &= 0x7FFFFFFF; // least significant 31 bits
  n &= 0x7FFFFFFF; // least significant 31 bits
}

std::string
mysql::password_digest4(const char* password, const char* salt)
{
  if (password == 0 || password[0] == 0 || salt == 0 || salt[0] == 0)
    return "";

  rand seed1;
  rand seed2;
  seed1.hash(password);
  seed2.hash(salt);

  rand rnd = seed1 ^ seed2;
  rnd.m %= 0x3FFFFFFF;
  rnd.n %= 0x3FFFFFFF;

  char* d = (char*)alloca(strlen(salt)+1);
  char* p = d;

  while(*salt++)
    *p++ = 64 + (int)(rnd.gen_rand() * 31.0);

  *p = 0;
  p = d;

  
  char extra = (int)(rnd.gen_rand() * 31.0);
  while(*p)
    *p++ ^= extra; 

  return std::string(d);
}

std::string
mysql::password_digest5(const char* password, const char* salt)
{
  if (password == 0 || password[0] == 0 || salt == 0 || salt[0] == 0)
    return "";

  socklib::sha1digest hash1;
  socklib::sha1digest hash2;
  socklib::sha1digest hash3;
  unsigned char   hash3input[40];

  hash1.digest(password, strlen(password));
  hash2.digest(hash1.digest_raw, 20);

  memcpy(hash3input, salt, 20);
  memcpy(hash3input+20, hash2.digest_raw, 20);

  hash3.digest(hash3input, 40);

  std::string pwd("");
  for(int i = 0; i < 20; i++)
    pwd.append(1, hash1.digest_raw[i] ^ hash3.digest_raw[i]);

  return pwd;
}
//////////////////////////////////////////////////////////
///////////////////////////////////mysql static functions

std::string
mysql::rlike_string(const char* expr, const char* str)
{
  // convert str to regular expression suitable for rlike
  // a word is any sequence of characters except whitespace; if quoted, any char between quotes

  std::string regex("");

  int quote = 0;
  bool startnew = true;
  int pc = 0;

  while(*str) {
    int c = *str++;

    if (startnew) {
      if (isspace(c)) continue;

      startnew = false;
      pc = ' ';
      if (c == '\'' || c == '\"') {
        quote = c;
        continue;
      }
    } else if (quote) {
      if (c == quote) {
        startnew = true;
        quote = 0;
        continue;
      }
    } else if (isspace(c)) {
      startnew = true;
      continue;
    }

    if (pc == ' ') {
      if (regex.size())
        regex.append("' and ");
      regex.append(1, '(');
      regex.append(expr);
      regex.append(") regexp '");
    }

    switch(c) {
    case '(':
    case ')':
    case '+':
    case '^':
    case '[':
    case ']':
    case '.':
    case '*':
      regex.append("\\\\");
      break;

    case '\'':
    case '\"':
      regex.append("\\");
      break;
    }

    regex.append(1, c);

    pc = c;
  }

  if (regex.size())
    regex.append(1, '\'');

  return regex;
}

std::string
mysql::quote_string(const char* str, int quote_char)
{
  int   len = (int)strlen(str);

  socklib::tmpbuffer tmpbuf(2*len+1);
  char* buf = tmpbuf.buffer;
  char* p = buf;
  char* e = buf + 2 * len;

  while(*str && p < e) {
    if (*str == '\\') *p++ = *str; // double backslash

    if (*str == quote_char) *p++ = *str;

    *p++ = *str++;
  }

  *p = 0;
  
  return std::string(buf);
}

std::string
mysql::purify_string(const char* str, int quote_char)
{
  int   len = (int)strlen(str);

  socklib::tmpbuffer tmpbuf(2*len+1);
  char* buf = tmpbuf.buffer;
  char* p = buf;
  char* e = buf + 2 * len;

  int pc = 0;

  while(*str && p < e) {
    if (isspace(*str)) {
      if (pc != ' ')  pc = *p++ = ' ';
      str++;
      continue;
    }

    if (*str == '\\') *p++ = *str; // double backslash

    if (*str == quote_char) *p++ = *str;

    pc = *p++ = *str++;
  }

  *p = 0;
  while(p-- > buf && isspace(*p)) *p = 0;

  p = buf;
  while(*p && isspace(*p)) p++;

  return std::string(p);
}

std::string
mysql::purify_sql_search_string(const char* str)
{
  // . trim string
  // . replace * with %
  // . replace ? with _
  int   len = (int)strlen(str);
  char* buf = (char*)alloca(len + 1);
  char* p = buf;
  char* e = buf+len;

  while(*str && isspace(*str)) str++;

  int pc = 0;
  while(*str && p < e) {
    int c = *str++;
    switch(c) {
    case '*': c = '%'; break;
    case '?': c = '_'; break;
    }

    if (pc == '%' && c == '%') continue;

    *p++ = pc = c;
  }

  *p = 0;
  while(p-- > buf && isspace(*p)) *p = 0;;

  return std::string(buf);
}

////////////////////////////////////////////////////////////////////
//////////////////////////////////Exceptions  //////////////////////

mysql::exception::exception(int errcode, const char* fmt, ...)
: errmsg("")
{
  va_list ap;
  va_start(ap, fmt);
  vseterrf(errcode, fmt, ap);
  va_end(ap);
}

mysql::exception::exception(const char* fmt, ...)
: errmsg("")
{
  va_list ap;
  va_start(ap, fmt);
  vseterrf(0, fmt, ap);
  va_end(ap);
}

void
mysql::exception::seterrf(int errcode, const char* fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  vseterrf(errcode, fmt, ap);
  va_end(ap);
}

void
mysql::exception::vseterrf(int errcode, const char* fmt, va_list ap)
{
  char buf[16386];

  _vsnprintf(buf, 16386, fmt, ap);

  char* p = buf;
  char* e = p+strlen(p);
  
  while(p = strchr(p, '$')) {
    switch(*(p+1)) {
    case 'e': 
      {
        // insert err
        const char* err = mysql::strerr(errcode);
        if (err == 0) err = "";

        int errlen = (int)strlen(err);
        memmove(p+errlen, p+2, e-p-2);
        memmove(p, err, errlen);
        p += errlen;
        e += errlen-2; // $e is removed
      }
      break;
    default:
      p++;
      break;
    }
  }

  *e = 0;

  errmsg = errmsg + std::string(buf);
}


static char* errstr[] = {
  "HASHCHK",
  "NISAMCHK", 
  "NO",
  "YES",
  "CANT_CREATE_FILE",
  "CANT_CREATE_TABLE",
  "CANT_CREATE_DB",
  "DB_CREATE_EXISTS",
  "DB_DROP_EXISTS",
  "DB_DROP_DELETE",
  "DB_DROP_RMDIR",
  "CANT_DELETE_FILE",
  "CANT_FIND_SYSTEM_REC",
  "CANT_GET_STAT",
  "CANT_GET_WD",
  "CANT_LOCK",
  "CANT_OPEN_FILE",
  "FILE_NOT_FOUND",
  "CANT_READ_DIR",
  "CANT_SET_WD",
  "CHECKREAD",
  "DISK_FULL",
  "DUP_KEY",
  "ERROR_ON_CLOSE",
  "ERROR_ON_READ",
  "ERROR_ON_RENAME",
  "ERROR_ON_WRITE",
  "FILE_USED",
  "FILSORT_ABORT",
  "FORM_NOT_FOUND",
  "GET_ERRNO",
  "ILLEGAL_HA",
  "KEY_NOT_FOUND",
  "NOT_FORM_FILE",
  "NOT_KEYFILE",
  "OLD_KEYFILE",
  "OPEN_AS_READONLY",
  "OUTOFMEMORY",
  "OUT_OF_SORTMEMORY",
  "UNEXPECTED_EOF",
  "CON_COUNT_ERROR",
  "OUT_OF_RESOURCES",
  "BAD_HOST_ERROR",
  "HANDSHAKE_ERROR",
  "DBACCESS_DENIED_ERROR",
  "ACCESS_DENIED_ERROR",
  "NO_DB_ERROR",
  "UNKNOWN_COM_ERROR",
  "BAD_NULL_ERROR",
  "BAD_DB_ERROR",
  "TABLE_EXISTS_ERROR",
  "BAD_TABLE_ERROR",
  "NON_UNIQ_ERROR",
  "SERVER_SHUTDOWN",
  "BAD_FIELD_ERROR",
  "WRONG_FIELD_WITH_GROUP",
  "WRONG_GROUP_FIELD",
  "WRONG_SUM_SELECT",
  "WRONG_VALUE_COUNT",
  "TOO_LONG_IDENT",
  "DUP_FIELDNAME",
  "DUP_KEYNAME",
  "DUP_ENTRY",
  "WRONG_FIELD_SPEC",
  "PARSE_ERROR",
  "EMPTY_QUERY",
  "NONUNIQ_TABLE",
  "INVALID_DEFAULT",
  "MULTIPLE_PRI_KEY",
  "TOO_MANY_KEYS",
  "TOO_MANY_KEY_PARTS",
  "TOO_LONG_KEY",
  "KEY_COLUMN_DOES_NOT_EXITS",
  "BLOB_USED_AS_KEY",
  "TOO_BIG_FIELDLENGTH",
  "WRONG_AUTO_KEY",
  "READY",
  "NORMAL_SHUTDOWN",
  "GOT_SIGNAL",
  "SHUTDOWN_COMPLETE",
  "FORCING_CLOSE",
  "IPSOCK_ERROR",
  "NO_SUCH_INDEX",
  "WRONG_FIELD_TERMINATORS",
  "BLOBS_AND_NO_TERMINATED",
  "TEXTFILE_NOT_READABLE",
  "FILE_EXISTS_ERROR",
  "LOAD_INFO",
  "ALTER_INFO",
  "WRONG_SUB_KEY",
  "CANT_REMOVE_ALL_FIELDS",
  "CANT_DROP_FIELD_OR_KEY",
  "INSERT_INFO",
  "INSERT_TABLE_USED",
  "NO_SUCH_THREAD",
  "KILL_DENIED_ERROR",
  "NO_TABLES_USED",
  "TOO_BIG_SET",
  "NO_UNIQUE_LOGFILE",
  "TABLE_NOT_LOCKED_FOR_WRITE",
  "TABLE_NOT_LOCKED",
  "BLOB_CANT_HAVE_DEFAULT",
  "WRONG_DB_NAME",
  "WRONG_TABLE_NAME",
  "TOO_BIG_SELECT",
  "UNKNOWN_ERROR",
  "UNKNOWN_PROCEDURE",
  "WRONG_PARAMCOUNT_TO_PROCEDURE",
  "WRONG_PARAMETERS_TO_PROCEDURE",
  "UNKNOWN_TABLE",
  "FIELD_SPECIFIED_TWICE",
  "INVALID_GROUP_FUNC_USE",
  "UNSUPPORTED_EXTENSION",
  "TABLE_MUST_HAVE_COLUMNS",
  "RECORD_FILE_FULL",
  "UNKNOWN_CHARACTER_SET",
  "TOO_MANY_TABLES",
  "TOO_MANY_FIELDS",
  "TOO_BIG_ROWSIZE",
  "STACK_OVERRUN",
  "WRONG_OUTER_JOIN",
  "NULL_COLUMN_IN_INDEX",
  "CANT_FIND_UDF",
  "CANT_INITIALIZE_UDF",
  "UDF_NO_PATHS",
  "UDF_EXISTS",
  "CANT_OPEN_LIBRARY",
  "CANT_FIND_DL_ENTRY",
  "FUNCTION_NOT_DEFINED",
  "HOST_IS_BLOCKED",
  "HOST_NOT_PRIVILEGED",
  "PASSWORD_ANONYMOUS_USER",
  "PASSWORD_NOT_ALLOWED",
  "PASSWORD_NO_MATCH",
  "UPDATE_INFO",
  "CANT_CREATE_THREAD",
  "WRONG_VALUE_COUNT_ON_ROW",
  "CANT_REOPEN_TABLE",
  "INVALID_USE_OF_NULL",
  "REGEXP_ERROR",
  "MIX_OF_GROUP_FUNC_AND_FIELDS",
  "NONEXISTING_GRANT",
  "TABLEACCESS_DENIED_ERROR",
  "COLUMNACCESS_DENIED_ERROR",
  "ILLEGAL_GRANT_FOR_TABLE",
  "GRANT_WRONG_HOST_OR_USER",
  "NO_SUCH_TABLE",
  "NONEXISTING_TABLE_GRANT",
  "NOT_ALLOWED_COMMAND",
  "SYNTAX_ERROR",
  "DELAYED_CANT_CHANGE_LOCK",
  "TOO_MANY_DELAYED_THREADS",
  "ABORTING_CONNECTION",
  "NET_PACKET_TOO_LARGE",
  "NET_READ_ERROR_FROM_PIPE",
  "NET_FCNTL_ERROR",
  "NET_PACKETS_OUT_OF_ORDER",
  "NET_UNCOMPRESS_ERROR",
  "NET_READ_ERROR",
  "NET_READ_INTERRUPTED",
  "NET_ERROR_ON_WRITE",
  "NET_WRITE_INTERRUPTED",
  "TOO_LONG_STRING",
  "TABLE_CANT_HANDLE_BLOB",
  "TABLE_CANT_HANDLE_AUTO_INCREMENT",
  "DELAYED_INSERT_TABLE_LOCKED",
  "WRONG_COLUMN_NAME",
  "WRONG_KEY_COLUMN",
  "WRONG_MRG_TABLE",
  "DUP_UNIQUE",
  "BLOB_KEY_WITHOUT_LENGTH",
  "PRIMARY_CANT_HAVE_NULL",
  "TOO_MANY_ROWS",
  "REQUIRES_PRIMARY_KEY",
  "NO_RAID_COMPILED",
  "UPDATE_WITHOUT_KEY_IN_SAFE_MODE",
  "KEY_DOES_NOT_EXITS",
  "CHECK_NO_SUCH_TABLE",
  "CHECK_NOT_IMPLEMENTED",
  "CANT_DO_THIS_DURING_AN_TRANSACTION",
  "ERROR_DURING_COMMIT",
  "ERROR_DURING_ROLLBACK",
  "ERROR_DURING_FLUSH_LOGS",
  "ERROR_DURING_CHECKPOINT",
  "NEW_ABORTING_CONNECTION",
  "DUMP_NOT_IMPLEMENTED",
  "FLUSH_MASTER_BINLOG_CLOSED",
  "INDEX_REBUILD",
  "MASTER",
  "MASTER_NET_READ",
  "MASTER_NET_WRITE",
  "FT_MATCHING_KEY_NOT_FOUND",
  "LOCK_OR_ACTIVE_TRANSACTION",
  "UNKNOWN_SYSTEM_VARIABLE",
  "CRASHED_ON_USAGE",
  "CRASHED_ON_REPAIR",
  "WARNING_NOT_COMPLETE_ROLLBACK",
  "TRANS_CACHE_FULL",
  "SLAVE_MUST_STOP",
  "SLAVE_NOT_RUNNING",
  "BAD_SLAVE",
  "MASTER_INFO",
  "SLAVE_THREAD",
  "TOO_MANY_USER_CONNECTIONS",
  "SET_CONSTANTS_ONLY",
  "LOCK_WAIT_TIMEOUT",
  "LOCK_TABLE_FULL",
  "READ_ONLY_TRANSACTION",
  "DROP_DB_WITH_READ_LOCK",
  "CREATE_DB_WITH_READ_LOCK",
  "WRONG_ARGUMENTS",
  "NO_PERMISSION_TO_CREATE_USER",
  "UNION_TABLES_IN_DIFFERENT_DIR",
  "LOCK_DEADLOCK",
  "TABLE_CANT_HANDLE_FULLTEXT",
  "CANNOT_ADD_FOREIGN",
};

static int errstr_count = sizeof(errstr)/sizeof(char*);

const char*
mysql::strerr(int errcode)
{
  errcode -= 1000;
  if (errcode < 0 || errcode >= errstr_count) return 0;
  
  return errstr[errcode];
}

///////////////////////////////////////////////////////////////////
//////////////////////////////STATIC FUNCTIONS

void
dump(const char* buf, int len, bool toserver)
{
  if (toserver) printf("---->to server (%d bytes)\n", len);
  else printf("<----from server (%d bytes)\n", len);

  char pb[17]; pb[16] = 0;
  int  padlen = len + (16 - len % 16);

  int i = 0;
  for(; i < len; i++) {
    if (i == 0) printf("%07d", i);
    else if (i % 16 == 0) {
      printf(" %s", pb);
      printf("\n%07d", i);
    }

    printf(" %02x", (int)(unsigned char)buf[i]);

    if (isprint((unsigned char)buf[i])) pb[i % 16] = buf[i];
    else pb[i % 16] = '.';
  }

  for(; i < padlen; i++) {
    if (i == 0 || i % 16 == 0) break;
    printf("   ");
    pb[i % 16] = 0;
  }

  printf(" %s\n\n", pb);
}
